package com.auth.system.controller;

import com.auth.config.token.AuthOAuth2TokenEndpoint;
import com.redis.RedisUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.InsufficientAuthenticationException;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.common.exceptions.InvalidClientException;
import org.springframework.security.oauth2.common.exceptions.InvalidGrantException;
import org.springframework.security.oauth2.common.exceptions.InvalidRequestException;
import org.springframework.security.oauth2.common.exceptions.UnsupportedGrantTypeException;
import org.springframework.security.oauth2.common.util.OAuth2Utils;
import org.springframework.security.oauth2.provider.ClientDetails;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.TokenRequest;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.security.Principal;
import java.util.Collections;
import java.util.Map;

/**
 * 身份令牌认证
 * 认证获取token接口自定义实现
 * @author: hw
 * @date: 2021/12/1 13:42
 */
@RestController
@RequestMapping("/api/oauth2.0")
@Slf4j
public class AuthAccessTokenController {

    public static final String AUTH_CODE_PREFIX = "AUTH_CODE_PREFIX";
    @Autowired
    private RedisUtil redisUtils;

    @Autowired
    AuthOAuth2TokenEndpoint authOAuth2TokenEndpoint;

    @GetMapping(value = "/token")
    public ResponseEntity<OAuth2AccessToken> postAccessToken(@RequestParam Map<String, String> parameters) {
        String code = parameters.get("code");
        Authentication principal = null;
        if(null == code){
            throw new InsufficientAuthenticationException("The mandatory field of code cannot be empty");
        }
        if (redisUtils.hasKey(AUTH_CODE_PREFIX+":"+code)){
            principal = (Authentication)redisUtils.get(AUTH_CODE_PREFIX+":"+code);
        }else {
            throw new InvalidGrantException("无效的授权代码: " + code);
        }
        if (!(principal instanceof Authentication)) {
            throw new InsufficientAuthenticationException( "没有客户端身份验证。尝试添加适当的身份验证过滤器.");
        }

        String clientId = getClientId(principal);
        ClientDetails authenticatedClient = authOAuth2TokenEndpoint.getClientDetailsService().loadClientByClientId(clientId);
        TokenRequest tokenRequest = authOAuth2TokenEndpoint.getOAuth2RequestFactory().createTokenRequest(parameters, authenticatedClient);

        if (clientId != null && !clientId.equals("")) {
            // 只有在客户端在此期间通过身份验证时才验证客户端详细信息
            if (!clientId.equals(tokenRequest.getClientId())) {
                // 只有在客户端在此期间通过身份验证时才验证客户端详细信息
                // 经过身份验证的客户端
                throw new InvalidClientException("给定的客户端appKey与已验证的客户端不匹配");
            }
        }
        if (authenticatedClient != null) {
            authOAuth2TokenEndpoint.getoAuth2RequestValidator().validateScope(tokenRequest, authenticatedClient);
        }
        if (!StringUtils.hasText(tokenRequest.getGrantType())) {
            throw new InvalidRequestException("缺少授权类型");
        }
        if (tokenRequest.getGrantType().equals("implicit")) {
            throw new InvalidGrantException("令牌端点不支持隐式授权类型");
        }

        if (isAuthCodeRequest(parameters)) {
            // 范围是在授权步骤中请求或确定的
            if (!tokenRequest.getScope().isEmpty()) {
                log.debug("传入令牌请求的清除范围");
                tokenRequest.setScope(Collections.<String> emptySet());
            }
        }

        if (isRefreshTokenRequest(parameters)) {
            // 刷新令牌有它自己的默认范围，所以我们应该忽略工厂在这里添加的任何内容
            tokenRequest.setScope(OAuth2Utils.parseParameterList(parameters.get(OAuth2Utils.SCOPE)));
        }

        OAuth2AccessToken token = authOAuth2TokenEndpoint.getTokenGranter().grant(tokenRequest.getGrantType(),tokenRequest);
        if (token == null) {
            throw new UnsupportedGrantTypeException("不支持的授权类型: " + tokenRequest.getGrantType());
        }

        ResponseEntity<OAuth2AccessToken> response = getResponse(token);
        return response;
    }

    /**
     * @param principal 当前身份验证主体
     * @return 当前身份验证主体
     */
    protected String getClientId(Principal principal) {
        Authentication client = (Authentication) principal;
        if (!client.isAuthenticated()) {
            throw new InsufficientAuthenticationException("客户端未认证.");
        }
        String clientId = client.getName();
        if (client instanceof OAuth2Authentication) {
            // Might be a client and user combined authentication
            clientId = ((OAuth2Authentication) client).getOAuth2Request().getClientId();
        }
        return clientId;
    }

    private boolean isAuthCodeRequest(Map<String, String> parameters) {
        return "authorization_code".equals(parameters.get("grant_type")) && parameters.get("code") != null;
    }

    private boolean isRefreshTokenRequest(Map<String, String> parameters) {
        return "refresh_token".equals(parameters.get("grant_type")) && parameters.get("refresh_token") != null;
    }

    private ResponseEntity<OAuth2AccessToken> getResponse(OAuth2AccessToken accessToken) {
        HttpHeaders headers = new HttpHeaders();
        headers.set("Cache-Control", "no-store");
        headers.set("Pragma", "no-cache");
        headers.set("Content-Type", "application/json;charset=UTF-8");
        return new ResponseEntity<OAuth2AccessToken>(accessToken, headers, HttpStatus.OK);
    }
}
